package myutil.mylist;

import java.util.Arrays;
import java.util.Iterator;

/**
 *
 * @author Hlib_Babii
 * @param <T> type of stored elements
 */
public class MyArrayList<T> implements MyList<T>, RandomAccess {
    
    final static int DEFAULT_SIZE = 1<<4;
    
    final static int MAX_SIZE = 1<<30;
    
    int size;
    
    /**
     * array elements stored in
     */
    Object[] data;
    
    /**
     * constructs empty list with default capacity
     */
    public MyArrayList() {
        data = new Object[DEFAULT_SIZE];
    }
    
    public MyArrayList(MyList c) {
        data = c.toArray();
        size = data.length;
    }
    
    /**
     * Construct list with the specified capacity
     * @param initialCapacity 
     */
    public MyArrayList(int initialCapacity){
        if (initialCapacity < 0) {
            initialCapacity = DEFAULT_SIZE;
        }
        data = new Object[initialCapacity];
        size = data.length;
    }
    
    public void ensureCapacity(int minCapacity) {
        enlargeCapacityIfNecessary(minCapacity);
    }
    
    public void trimToSize() {
        if (size < data.length) {
            data = Arrays.copyOf(data, size);
        }
    }

    /**
     * Adds the specified object to the this list
     * @param e object to be added
     */
    @Override
    public void add(T e) {
        enlargeCapacityIfNecessary(size + 1);
        data[size++] = e;
    }

    @Override
    public void add(int index, T e) {
        if (index != 0) {
            checkIndex(index-1);
        }
        
        enlargeCapacityIfNecessary(size + 1);
        System.arraycopy(data, index, data, index+1, size - index);
        ++size;
        data[index] = e;
    }

    @Override
    public void addAll(T[] c) {
        final int nNewElements = c.length;
        enlargeCapacityIfNecessary(size + nNewElements);
        System.arraycopy(c, 0, data, size, nNewElements);
        size += nNewElements;
    }

    @Override
    public void addAll(int index, T[] c) {
        if (index != 0) {
            checkIndex(index - 1);
        }
        
        final int nNewElements = c.length;
        enlargeCapacityIfNecessary(size + nNewElements);
        System.arraycopy(data, index, data, index + nNewElements, size - index);
        System.arraycopy(c, 0, data, index, nNewElements);
        size += nNewElements;
    }

    
    @Override
    public T get(int index) {
        return getInner(index);
    }
    
    
    /**
     * Removes and returns the element with the index specified from this list
     * @param index index of the element to be removed
     * @return the element with the index specified from this list
     */
    @Override
    public T remove(int index) {
        final T toReturn = getInner(index); // range check is inside       
        System.arraycopy(data, index+1, data, index, size - index - 1);
        data[--size] = null;
        return toReturn;
    }
    
    /**
     * Removes all the elements from this list
     */
    @Override
    public void clear() {
        for (int i = 0; i < size; i++) {
            data[i] = null;
        }
        size = 0;
    }

    /**
     * Tests whether this list is empty
     * @return true if this list is empty, false otherwise
     */
    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    
    @Override
    public void set(int index, T e) {
        checkIndex(index);
        data[index] = e;
    }

    /**
     * Returns the index of the specified object in this list
     * @param o object to find index for
     * @return the index of the specified object in this list
     */
    @Override
    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++) {
                if (data[i] == null) {
                    return i;
                }
            }
        } else {
            for (int i = 0; i < size; i++) {
                if (data[i].equals(o)) {
                    return i;
                }                
            }
        }
        return -1;
    }

    /**
     * Returns the number of elements in the array
     * @return the number of elements in the array
     */
    @Override
    public int size() {
        return size;
    }

    @Override
    public Object[] toArray() {
        return Arrays.copyOf(data, size);
    }

    /**
     * Returns the iterator over the list
     * @return the iterator over the list
     */
    @Override
    public Iterator<T> iterator() {
        return new MyIterator();
    }
    
    class MyIterator implements Iterator<T> {
        
        private int curPos = 0;
                
        private boolean lastReturnedValid = false; 

        @Override
        public boolean hasNext() {
            return curPos < size;
        }

        @Override
        public T next() {
            try {
                T toReturn = getInner(curPos++);
                lastReturnedValid = true;
                return toReturn;
	    } catch (MyIndexOutOfBoundsException ex) {
	        throw new MyNoSuchElementException();
            }
        }

        @Override
        public void remove() {
            if (lastReturnedValid) {
                MyArrayList.this.remove(--curPos);
                lastReturnedValid = false;
            }           
        }

    }
    
    // private section
    
    private void enlargeCapacityIfNecessary(int minCapacity) {
        if (minCapacity <= data.length) {
            return; // already ensured
        } 
        
        int newCapacity = data.length >> 1;
        if (newCapacity < minCapacity) {
            newCapacity = minCapacity;
        }
        if (newCapacity > MAX_SIZE) {
            newCapacity = MAX_SIZE;
        }
        data = Arrays.copyOf(data, newCapacity);        
    }

    /**
     * checks if index within bounds
     * @param index index to be checked
     * @throws MyIndexOutOfBoundsException if {@code index}
     * is not within bounds
     */
    private void checkIndex(int index) {
        if (index < 0 || index >= size) {
            throw new MyIndexOutOfBoundsException();
        }
    }
    
    @SuppressWarnings("unchecked")
    private T getInner(int index) {
        checkIndex(index);
        return (T) data[index];
    }
    
}
